package com.imgo.max.fans.listener;

import com.imgo.max.artist.constant.FansConstant;
import com.imgo.max.cache.util.CacheCommonManager;
import com.imgo.max.cache.util.DataInitOkList;
import com.imgo.max.common.util.page.Pagination;
import com.imgo.max.fans.dao.FocusArtistDao;
import com.imgo.max.fans.mapping.FocusArtist;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.*;

/**
 * @Auth darren.dai
 * @Date 6/8/16.
 */
@Component
public class FansMsgHandler {

    private static final Logger logger = LoggerFactory.getLogger(FansMsgHandler.class);

    @Resource
    private RedisTemplate stringSerializerRedisTemplate;
    @Resource
    private FocusArtistDao focusArtistDao;
    @Resource
    CacheCommonManager cacheCommonManager;

    private DataInitOkList fansDataInitOkList;
    private DataInitOkList followDataInitOkList;


    private static final String FANS_DATA_INIT_OK_FLAG_SAVE_KEY = "fans:fans:data:init:ok:complete:key";
    private static final String FOLLOWS_DATA_INIT_OK_FLAG_SAVE_KEY = "fans:follows:data:init:ok:complete:key";


    private boolean isRedisDataInitOk(String uid, boolean isInitFans) {
        boolean result = false;
        String key = getZSetKey(uid, isInitFans);
        String zsetKey = FOLLOWS_DATA_INIT_OK_FLAG_SAVE_KEY;
        if (isInitFans) {
            zsetKey = FANS_DATA_INIT_OK_FLAG_SAVE_KEY;
        }
        DataInitOkList tmpDataInitOkList = getDataInitOkList(isInitFans);
        try {
            if (tmpDataInitOkList.size() > 0) {
                result = tmpDataInitOkList.containsNew(key);
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        try {
            if (!result) { // 本地没有存储活跃的key,查询Redis
                result = cacheCommonManager.sIsMember(zsetKey, key,
                        CacheCommonManager.RedisTemplateEnum.STRINGSERIALIZER);
                if (result) {
                    tmpDataInitOkList.leftPush(key);
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return result;
    }

    private DataInitOkList getDataInitOkList(boolean isInitFans) {
        if (isInitFans) {
            if (fansDataInitOkList == null) {
                fansDataInitOkList = new DataInitOkList();
            }
            return fansDataInitOkList;
        }
        if (followDataInitOkList == null) {
            followDataInitOkList = new DataInitOkList();
        }
        return followDataInitOkList;
    }

    private void addDataToDataInitOkList(String key, boolean isFans) {
        DataInitOkList dataInitOkList = getDataInitOkList(isFans);
        dataInitOkList.add(key);
    }

    private void addInitOkKey2RedisSet(String key, String value) {
        cacheCommonManager.sAdd(CacheCommonManager.RedisTemplateEnum.STRINGSERIALIZER, key, value);

    }

    /**
     * 初始化粉丝数据
     *
     * @param artistId
     */
    public void initFansRedisSetData(String artistId) {
        if (StringUtils.isBlank(artistId) || artistId.length() < 32) {
            return;
        }
        String redisZSetArtistKey = getZSetKey(artistId, true);
        ZSetOperations zSetOperations = getZSetOperations();
        try {
            List<FocusArtist> focusArtistsList = focusArtistDao.getFansListByUid(artistId, null);
            setRedisZSetData(zSetOperations, redisZSetArtistKey, focusArtistsList, false);
            if (focusArtistsList.size() >= 0) {
                addDataToDataInitOkList(redisZSetArtistKey, true);
                addInitOkKey2RedisSet(FANS_DATA_INIT_OK_FLAG_SAVE_KEY, redisZSetArtistKey);
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }

    /**
     * 初始化关注艺人数据
     *
     * @param uid
     */
    public void initCollectArtistRedisSetData(String uid) {
        if (StringUtils.isBlank(uid) || uid.length() < 32) {
            return;
        }
        String redisUserKey = getZSetKey(uid, false);
        ZSetOperations setOperations = getZSetOperations();
        try {
            List<FocusArtist> focusArtistsList = focusArtistDao.selectByUid(uid, null);
            setRedisZSetData(setOperations, redisUserKey, focusArtistsList, true);
            if (focusArtistsList.size() >= 0) {
                addDataToDataInitOkList(redisUserKey, false);
//                cacheCommonManager.sAdd(CacheCommonManager.RedisTemplateEnum.STRINGSERIALIZER,
                addInitOkKey2RedisSet(FOLLOWS_DATA_INIT_OK_FLAG_SAVE_KEY, redisUserKey);
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }

    private void setRedisZSetData(ZSetOperations setOperations, String redisKey, List<FocusArtist> focusArtistsList, boolean
            isBefocusId) {
        Set<ZSetOperations.TypedTuple<String>> data = createRedisZSetData(focusArtistsList, isBefocusId);
        if (data.size() > 0) {
            setOperations.add(redisKey, data);
        }
    }

    private Set<ZSetOperations.TypedTuple<String>> createRedisZSetData(List<FocusArtist> focusArtistsList, boolean
            isBefocusId) {
        Set<ZSetOperations.TypedTuple<String>> result = new HashSet<>();
        if (CollectionUtils.isNotEmpty(focusArtistsList)) {
            ZSetOperations.TypedTuple<String> tuple;
            for (FocusArtist focusArtist : focusArtistsList) {
                if (focusArtist != null) {
                    if (isBefocusId) {
                        if (StringUtils.isNotBlank(focusArtist.getBefocus_uid())) {
                            tuple = createTypedTuple(focusArtist.getBefocus_uid(), focusArtist.getFocus_time());
                            result.add(tuple);
                        }
                    } else {
                        if (StringUtils.isNotBlank(focusArtist.getFocus_uid())) {
                            tuple = createTypedTuple(focusArtist.getFocus_uid(), focusArtist.getFocus_time());
                            result.add(tuple);
                        }
                    }
                }
            }
        }
        return result;
    }

    private ZSetOperations.TypedTuple<String> createTypedTuple(String uid, Date date) {
        ZSetOperations.TypedTuple tuple = new DefaultTypedTuple(uid, new Double(date.getTime()));
        return tuple;
    }

    public void cancelCollectArtistRedisOperate(String uid, String artistId) {
        redisOperate(uid, artistId, 0);
    }

    public void collectArtistRedisOperate(String uid, String artistId) {
        redisOperate(uid, artistId, 1);
    }

    private void redisOperate(String uid, String artistId, final int operateType) {
        try {
            String redisUserKey = getZSetKey(uid, false);
            String redisArtistKey = getZSetKey(artistId, true);
            RedisSerializer keySerializer = stringSerializerRedisTemplate.getKeySerializer();
            RedisSerializer valueSerializer = stringSerializerRedisTemplate.getValueSerializer();
            final byte[] userKeyByte = cacheCommonManager.rawKey(keySerializer, redisUserKey);
            final byte[] artistKeyByte = cacheCommonManager.rawKey(keySerializer, redisArtistKey);
            final byte[] userValueByte = cacheCommonManager.rawValue(valueSerializer, uid);
            final byte[] artistValueByte = cacheCommonManager.rawValue(valueSerializer, artistId);
            Date date = new Date();
            final long score = date.getTime();

            if (operateType == 1) {// 初始化关注数据
                boolean isInitOk = isRedisDataInitOk(artistId, true);
                if (!isInitOk) {
                    initFansRedisSetData(artistId);
                }
                isInitOk = isRedisDataInitOk(uid, false);
                if (!isInitOk) {
                    initCollectArtistRedisSetData(uid);
                }
            }

            stringSerializerRedisTemplate.executePipelined(new RedisCallback<Object>() {
                /**
                 * Gets called by {@link RedisTemplate} with an active Redis connection. Does not need to care about activating or
                 * closing the connection or handling exceptions.
                 *
                 * @param connection active Redis connection
                 * @return a result object or {@code null} if none
                 * @throws DataAccessException
                 */
                @Override
                public Object doInRedis(RedisConnection connection) throws DataAccessException {
                    if (operateType == 0) {
                        connection.zRem(userKeyByte, artistValueByte);
                        connection.zRem(artistKeyByte, userValueByte);
                    } else if (operateType == 1) {
                        connection.zAdd(userKeyByte, score, artistValueByte);
                        connection.zAdd(artistKeyByte, score, userValueByte);
                    }
                    return null;
                }
            });
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }

    /**
     * 返回用户关注艺人数量
     *
     * @param uid
     * @return
     */
    public long getFollowingSize(String uid) {
        String key = getZSetKey(uid, false);
        ZSetOperations setOperations = getZSetOperations();
        long size = setOperations.size(key);
        if (size > 0) {
            return size;
        }
        // 关注艺人数据为0时查询数据库
        initCollectArtistRedisSetData(uid);
        size = setOperations.size(key);
        return size;
    }

    public boolean isFollowing(String uid, String artistId) {
        String key = getZSetKey(uid, false);
        ZSetOperations setOperations = getZSetOperations();
        Double result = setOperations.score(key, artistId);
        if (result == null) {
            long size = setOperations.size(key);
            if (size <= 0) {
                boolean isInitOk = isRedisDataInitOk(uid, false);
                if (!isInitOk) {
                    initCollectArtistRedisSetData(uid);
                }
                result = setOperations.score(key, artistId);
            }
            return false;
        }
        return true;
    }

    /**
     * 返回艺人粉丝数量
     *
     * @param artistId
     * @return
     */
    public long getFansIdsSize(String artistId) {
        String key = FansConstant.REDIS_ARTIST_FANS_IDS_ZSET_PREFIX + artistId;
        Set<String> artistIdSet = new HashSet<>();
        artistIdSet.add(key);
        long size = getArtistFansSize(artistIdSet);
        if (size > 0) {
            return size;
        }
        // fans数量为0时查询数据库
        boolean isInitOk = isRedisDataInitOk(artistId, true);
        if (!isInitOk) {
            initFansRedisSetData(artistId);
        }
        size = getArtistFansSize(artistIdSet);
        return size;
    }

    private long getArtistFansSize(Set<String> artistIdSet) {
        List<Object> redisResult = cacheCommonManager.getZSetLength(artistIdSet);
        int index = 0;
        Long fansNum = null;
        if (redisResult != null && index < redisResult.size()) {
            fansNum = (Long) redisResult.get(index);
        }
        if (fansNum == null || fansNum.longValue() < 0) {
            fansNum = 0L;
        }
        return fansNum;
    }

    public Set<String> getZSetRedisValues(String uid, boolean isArtistId, int start, int end) {
        String key = getZSetKey(uid, isArtistId);
        ZSetOperations setOperations = getZSetOperations();
        Set<String> result = setOperations.reverseRange(key, start, end);
        return result;
    }

    public Set<DefaultTypedTuple<String>> getRangeWithScores(String uid, boolean isArtistId, int start, int end) {
        String key = getZSetKey(uid, isArtistId);
        ZSetOperations setOperations = getZSetOperations();
        Set<DefaultTypedTuple<String>> data = setOperations.reverseRangeWithScores(key, start, end);
        if (data.size() <= 0) {
            // 确认数据已经初始化
            if (isArtistId) {
                initFansRedisSetData(uid);
            } else {
                initCollectArtistRedisSetData(uid);
            }
            data = setOperations.reverseRangeWithScores(key, start, end);
        }
        return data;
    }

    public List<String> getIdsByRange(String uid, boolean isArtistId, int start, int end) {
        List<String> uidList = new ArrayList<>();
        Set<DefaultTypedTuple<String>> data = getRangeWithScores(uid, isArtistId, start, end);
        if (data.size() > 0) {
            getUidList(data, uidList);
        }
        return uidList;
    }

    /**
     * 关注艺人ID列表
     *
     * @param uid
     * @return
     */
    public List<String> getIds(String uid, boolean isArtistId, Pagination pagination) {
        List<String> uidList = new ArrayList<>();
        String key = getZSetKey(uid, isArtistId);
        ZSetOperations setOperations = getZSetOperations();
        long start = 0;
        long end = -1;
        if (pagination != null) {
            start = pagination.getCurrentResult();
            end = start + pagination.getRows() - 1;
        }
        Set<DefaultTypedTuple<String>> data = setOperations.reverseRangeWithScores(key, start, end);
        if (data.size() > 0) {
            getUidList(data, uidList);
        } else {
            // 确认数据已经初始化
            if (isArtistId) {
                initFansRedisSetData(uid);
            } else {
                initCollectArtistRedisSetData(uid);
            }
            data = setOperations.reverseRangeWithScores(key, start, end);
            getUidList(data, uidList);
        }
        return uidList;
    }


    private void initRedisData(String uid, boolean isArtistId) {
        boolean isInitOk = isRedisDataInitOk(uid, true);
        if (!isInitOk) {
            if (isArtistId) {
                initFansRedisSetData(uid);
            } else {
                initCollectArtistRedisSetData(uid);
            }
        }
    }

    public void getUidList(Set<DefaultTypedTuple<String>> data, List<String> uidList) {
        if (data.size() > 0) {
            for (DefaultTypedTuple<String> typedTuple : data) {
                if (typedTuple != null && StringUtils.isNotBlank(typedTuple.getValue())) {
                    uidList.add(typedTuple.getValue());
                }
            }
        }
    }

    public void getUidTimeMap(Set<DefaultTypedTuple<String>> data, Map<String, Date> uidDateMap) {
        if (data.size() > 0) {
            for (DefaultTypedTuple<String> typedTuple : data) {
                if (typedTuple != null && StringUtils.isNotBlank(typedTuple.getValue())) {
                    uidDateMap.put(typedTuple.getValue(), new Date(typedTuple.getScore().longValue()));
                }
            }
        }
    }

    private ZSetOperations getZSetOperations() {
        return stringSerializerRedisTemplate.opsForZSet();
    }

    private String getZSetKey(String uid, boolean isArtistId) {
        String key;
        if (isArtistId) {
            key = FansConstant.REDIS_ARTIST_FANS_IDS_ZSET_PREFIX + uid;
        } else {
            key = FansConstant.REDIS_USER_FOLLOWING_ARTIST_ZSET_PREFIX + uid;
        }
        return key;
    }

}
